package vista.objetos;

import java.awt.Color;
import java.awt.Font;
import java.awt.Point;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import javax.media.opengl.awt.GLJPanel;

import controlador.ContenedorDeErrores;
import controlador.ControladorDeTiposDeObjetos;

import logica.Atributo;
import logica.Entidad;
import logica.ObjetoDiagramaConAtributo;

import eventos.EventoEtiquetaDeGraficable;

import vista.Tema;
import vista.paneles.hojaDibujo.HojaDeDibujo;

public abstract class GraficableConEtiquetaConAtributos extends GraficableConEtiqueta{
	
	private List<AtributoGraficable> atributos = new LinkedList<AtributoGraficable>();
	private List<EntidadRolGraficable> referenciasExternas = new LinkedList<EntidadRolGraficable>();

	public GraficableConEtiquetaConAtributos() {
		super();
	}
	
	public List<AtributoGraficable> getAtributos() {
		return atributos;
	}

	public void setAtributos(List<AtributoGraficable> atributos) throws AtributoTipoNoSoportadoException, GraficableException 
	{
		verificarConsistenciaDeAtributos(atributos);
		doSetAtributos(atributos);
	}

	void doSetAtributos(List<AtributoGraficable> atributos) {
		this.atributos = atributos;
	}
	
	public void verificarConsistenciaDeAtributos(List<AtributoGraficable> atributosGraf) throws AtributoTipoNoSoportadoException, GraficableException 
	{
		try 
		{
			List<AtributoGraficable> atributoGrafCorrectos = ControladorDeTiposDeObjetos.getInstancia().getAtributoGraficables(
					((ObjetoDiagramaConAtributo)this.getObjetoDiagramaDecorado()).getAtributos());

			if (atributoGrafCorrectos.size() != atributosGraf.size())
				throw new GraficableException("Incosistencia entre la cantidad de atributos lógicos y la cantidad de atributos gráficos.");
			
			for (AtributoGraficable atributoGrafCorrecto: atributoGrafCorrectos)
				buscarAtributoCorrecto(atributosGraf, atributoGrafCorrecto);
		} 
		catch (NoEsObjetoDiagramableException e) 
		{// se supone q no puede pasar
			e.printStackTrace();
		}
	}

	private void buscarAtributoCorrecto(List<AtributoGraficable> atributosGraf, AtributoGraficable atributoGrafCorrecto) throws GraficableException 
	{
		for (AtributoGraficable atributoGraf: atributosGraf)
			if (atributoGraf.equals(atributoGrafCorrecto))
				return;
		
		throw new GraficableException("No se halló un atributo graficable coincidente con el atributo lógico: "+ atributoGrafCorrecto.getAtributo().getNombre()+".");
	}
	
	@Override
	public void agregarEventoEtiqueta(HojaDeDibujo hoja)
	{
		super.agregarEventoEtiqueta(hoja);		
    	this.agregarEventoEtiquetaAtributo(hoja);
	}
	
	@Override
	protected EventoEtiquetaDeGraficable getEvento(HojaDeDibujo hoja) {
		return new EventoEtiquetaDeGraficable(hoja, this);
	}

	private void agregarEventoEtiquetaAtributo(HojaDeDibujo hoja) 
	{
		Iterator<AtributoGraficable> iteradorAtributos = this.getAtributos().iterator();
    	
    	while (iteradorAtributos.hasNext())
    	{
    		AtributoGraficable atributo = iteradorAtributos.next();
    		atributo.agregarEventoEtiqueta(hoja);
    	}
	}
	
	@Override
	public void agregarseAlCanvas(GLJPanel panel, Font fuente, Color color)
	{	      
		super.agregarseAlCanvas(panel, fuente, Tema.getInstancia().getFuenteColor());
		super.setUbicacionEtiqueta(this.getPunto().x - this.getEtiqueta().getWidth()/2, this.getPunto().y - this.getEtiqueta().getHeight()/2);
    	this.agregarAlCanvasAtributos(panel, fuente);
	}

	private void agregarAlCanvasAtributos(GLJPanel panel, Font fuente) 
	{
		for (AtributoGraficable atributo: this.getAtributos()) 
		{
			atributo.setPuntoCentroEntidad(this.getPunto());
			atributo.setUbicacionCirculo();
            atributo.agregarseAlCanvas(panel, fuente, Tema.getInstancia().getFuenteColor());
		}
	}

	@Override
	public void inicializarPosiciones()
	{
		this.setActualizarPosiciones(this.getPunto().x - this.getEtiqueta().getWidth()/2 , this.getPunto().y - this.getEtiqueta().getHeight()/2);
		EstrategiaDeDistribucionDeAtributos.calcularPosicionAtributoDefault(this.getAtributos(), this.getPunto());
	}
	
	public void setUbicacionEtiqueta(int nuevoX, int nuevoY)
	{
		Point posicionAnteriorEntidad = this.getEtiqueta().getLocation();
		Point desplazamiento = new Point();
		
		desplazamiento.x = nuevoX - posicionAnteriorEntidad.x;
		desplazamiento.y = nuevoY - posicionAnteriorEntidad.y;
		
		this.setActualizarPosiciones(nuevoX, nuevoY);

		setUbicacionEtiquetaDeAtributos(desplazamiento);
	}

	private void setUbicacionEtiquetaDeAtributos(Point desplazamiento)
	{
		for (AtributoGraficable atributo: this.getAtributos()) 
		{
            atributo.setUbicacionEtiqueta(desplazamiento.x, 
            		desplazamiento.y);
		}
	}
	
	private void setActualizarPosiciones(int x, int y)
	{
		super.setUbicacionEtiqueta(x, y);
		this.getPunto().setLocation(x + this.getEtiqueta().getWidth()/2, y + this.getEtiqueta().getHeight()/2);
	}
	
	@Override
	public void borrarseDeLaHoja() 
	{
		if (this.getDiagrama().soyElDiagramaActual())
		{
			super.borrarseDeLaHoja();
			borrarAtributos();
		}
	}

	public void borrarAtributos() 
	{
		for (AtributoGraficable atributo: this.getAtributos())
			atributo.borrarseDeLaHoja();
	}
	
	protected void copiarAtributos(GraficableConEtiquetaConAtributos graficableCopia)
	{
		Iterator<AtributoGraficable> iteratorAtributo = this.getAtributos().iterator();
		List<AtributoGraficable> listDeLaCopia = graficableCopia.getAtributos();
		
		while (iteratorAtributo.hasNext()) 
			listDeLaCopia.add((AtributoGraficable)iteratorAtributo.next().copiarse());
	}
	
	protected List<AtributoGraficable> copiarAtributos(Point nuevoCentro)
	{
		List<AtributoGraficable> atributosCopia = new LinkedList<>();
		AtributoGraficable atributoCopia;
		
		for (AtributoGraficable atributo: this.getAtributos()) 
		{
			atributoCopia = atributo.copiarse();
			atributoCopia.setPuntoCentroEntidad(nuevoCentro);
			atributosCopia.add(atributoCopia);
		}
		
		return atributosCopia;
	}
	
	protected void setPuntoMedioAtributo()
	{
		for (AtributoGraficable atributo: this.getAtributos()) 
			 atributo.setPuntoCentroEntidad(this.getPunto());
	}
	
	@Override
	public void cambiarEscala(int nro)
	{
		super.cambiarEscala(nro);
		
		Iterator<AtributoGraficable> iteratorAtributo = this.getAtributos().iterator();

		while (iteratorAtributo.hasNext()) 
		{
			AtributoGraficable atributo = (AtributoGraficable) iteratorAtributo.next();
			atributo.cambiarEscala(nro);
		}
	}

	public List<EntidadRolGraficable> getReferenciasExternas() {
		return referenciasExternas;
	}
	
	protected void setReferenciasExternas(List<EntidadRolGraficable> referenciasExternas) {
		this.referenciasExternas = referenciasExternas;
	}
	
	@Override
	public void validar(ContenedorDeErrores errores)
	{
		ObjetoDiagramaConAtributo objeto;
		try 
		{
			objeto = (ObjetoDiagramaConAtributo) this.getObjetoDiagramaDecorado();
			
			if (objeto.getAtributos().size() != this.getAtributos().size())
				errores.agregarError("Diagrama graficable: <<" + this.getDiagrama().getDiagrama().getNombre() + ">> - En la " + this.getClass() +" de nombre <<" + objeto.getNombre() + ">> no coincide la cantidad de atributos gráficos (" + this.getAtributos().size() + ") y lógicos (" + objeto.getAtributos().size() + ").");
			
			if (objeto.getReferenciaAEntidadesExternas().size() != this.getReferenciasExternas().size())
				errores.agregarError("Diagrama graficable: <<" + this.getDiagrama().getDiagrama().getNombre() + ">> - En la " + this.getClass() +" de nombre <<" + objeto.getNombre() + ">> no coincide la cantidad de referencias externas graficables (" + this.getReferenciasExternas().size() + ") y lógicas (" + objeto.getReferenciaAEntidadesExternas().size() +").");
			
			for (Entidad entidad: objeto.getReferenciaAEntidadesExternas())
				verificarReferenciasExternas(entidad, errores);
			
			for (Atributo atributo: objeto.getAtributos())
				verificarAtributos(objeto, atributo, errores);

		} catch (NoEsObjetoDiagramableException e) { //no debería pasar
			e.printStackTrace();
		}
	}

	private void verificarReferenciasExternas(Entidad entidad, ContenedorDeErrores errores) throws NoEsObjetoDiagramableException 
	{
		for (EntidadRolGraficable entidadRol: this.getReferenciasExternas())
			if (entidadRol.getEntidad().getObjetoDiagramaDecorado() == entidad)
				return;
		
		errores.agregarError("Diagrama graficable: <<" + this.getDiagrama().getDiagrama().getNombre() + ">> - No se encontró una entidad graficable que represente a la entidad lógica <<" + entidad.getNombre() + ">> .");
	}
	
	private void verificarAtributos(ObjetoDiagramaConAtributo objeto, Atributo atributo, ContenedorDeErrores errores) 
	{
		for (AtributoGraficable atributoGraficable: this.getAtributos())
			if (atributoGraficable.getAtributo() == atributo)
				return;
		
		errores.agregarError("Diagrama graficable: <<" + this.getDiagrama().getDiagrama().getNombre() + ">> - En la " + this.getClass() +" de nombre <<" + objeto.getNombre() + ">> no se encontró un atributo graficable que represente al atributo lógica <<" + atributo.getNombre() + ">> .");
		
	}
}
